// OpentTxl-C Version 11 profiiler
// J.R. Cordy, Jan 2023

// Copyright 2023, James R. Cordy and others

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
// and associated documentation files (the “Software”), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies 
// or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
// AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// The TXL rule and grammar profile analyzer.
// Analyzes and outputs a summary table of the raw profile data produced by a TXL run
// Usage: txlapr [-parse | -rules] [-time] [-space] [-calls] [-cycles] [-percall]

// Modification Log

// v11.0 Initial revision, adapted from OpenTxl 11.0

// I/O, strings, memory allocation
#include "support.h"

// Current version
#define version "OpenTxl-C Profiler v11.3 (4.12.23) (c) 2023 James R. Cordy and others" 

// Indirect dependency!
#define maxRules 4096

// Rule statistics table
struct ruleStatsT {
    string name;
    int calls;
    int matches;
    int searchcycles;
    int matchcycles;
    int trees;
    int kids;
};
// 1-origin [1 .. maxRules]
struct ruleStatsT ruleStats[maxRules + 1];

// Main analysis program
void TProg (void) {
    fprintf (stderr, "%s\n", version);

    // Get command line options
    int infile = 0;
    bool parse = false;
    bool bytime = false;
    bool  byspace = false;
    bool bycalls = false;
    bool bycycles = false;
    bool  byname = false;
    bool  byeff = false;
    bool  percall = false;

    // Default profile rules by time
    bytime = true;

    const int nargs = tfargcount;

    for (int a = 1; a <= nargs; a++) {
        string arg;
        tffetcharg (a, arg);

        if (stringcmp (arg, "-time") == 0) {
            bytime = true;
            byname = false;
            byspace = false;
            bycalls = false;
            bycycles = false;
            byeff = false;

        } else if (stringcmp (arg, "-space") == 0) {
            bytime = false;
            byname = false;
            byspace = true;
            bycalls = false;
            bycycles = false;
            byeff = false;

        } else if (stringcmp (arg, "-calls") == 0) {
            bytime = false;
            byname = false;
            byspace = false;
            bycalls = true;
            bycycles = false;
            byeff = false;

        } else if (stringcmp (arg, "-cycles") == 0) {
            bytime = false;
            byname = false;
            byspace = false;
            bycalls = false;
            bycycles = true;
            byeff = false;

        } else if (stringcmp (arg, "-eff") == 0) {
            bytime = false;
            byname = false;
            byspace = false;
            bycalls = false;
            bycycles = false;
            byeff = true;

        } else if (stringcmp (arg, "-percall") == 0) {
            percall = true;

        } else if (stringcmp (arg, "-parse") == 0) {
            parse = true;

        } else if (stringchar (arg, 1) == '-') {
            fprintf (stderr, "TXL Profiler: Invalid flag '%s'", arg);
            fprintf (stderr, " (options are -parse, -time, -space, -calls, -cycles, -eff, -percall)\n");
            throw (QUIT);

        } else {
            // Explicitly given previous profile file
            if (infile == 0) {
                tfopen (OPEN_CHAR_READ, arg, &infile);

                if (infile == 0) {
                    fprintf (stderr, "TXL Profiler: Unable to open TXL profile file '%s'\n", arg);
                    throw (QUIT);
                }

            } else {
                fprintf (stderr, "TXL Profiler: Only one TXL profile file allowed\n");
                throw (QUIT);
            }
        }
    }

    // Implicit default profile file 
    if (infile == 0) {
        if (parse) {
            tfopen (OPEN_CHAR_READ, "txl.pprofout", &infile);
        } else {
            tfopen (OPEN_CHAR_READ, "txl.rprofout", &infile);
        }

        if (infile == 0) {
            fprintf (stderr, "TXL Profiler: Unable to open TXL profile file 'txl.rprofout' or 'txl.pprofout'\n");
            fprintf (stderr, "  (Probable cause: errors in profiled TXL run)\n");
            throw (QUIT);
        }
    }

    // Rule statistics
    int nrules = 0;

    // Flush title
    string dummy;
    tfgetstring (dummy, infile);

    // Get stats from profile file
    for (int r = 1; r <= maxRules; r++) {
        if (tfeof (infile)) break;

        struct ruleStatsT *rule = &(ruleStats[r]);
        tfscanf (infile, "%s ", rule->name);

        if (stringcmp (rule->name, "") == 0) break;

        tfscanf (infile, "%d", &(rule->calls));
        tfscanf (infile, "%d", &(rule->matches));
        tfscanf (infile, "%d", &(rule->searchcycles));
        tfscanf (infile, "%d", &(rule->matchcycles));
        tfscanf (infile, "%d", &(rule->trees));
        tfscanf (infile, "%d", &(rule->kids));

        if (parse && ((stringncmp (rule->name, "repeat__", 8) == 0) || (stringncmp (rule->name, "list__", 6) == 0))) {
            const int zindex = stringindex (rule->name, "__");
            string postz;
            substring (postz, rule->name, zindex + 2, stringlen (rule->name));
            substring (rule->name, rule->name, 1, zindex - 1);
            stringcat (rule->name, postz); 
        }

        nrules += 1;
    }

    tfclose (infile);

    // Sort rules/nonterms by time or as desired
    for (int i = 1; i <= nrules - 1; i++) {

        for (int j = nrules - 1; j >= i; j--) {
            double jeff, j1eff;

            if (parse && byeff) {
                if (ruleStats[j].searchcycles > 0) {
                    jeff = (((double) (ruleStats[j].trees))  / ((double) (ruleStats[j].searchcycles))) * 100;
                } else {
                    jeff = 0;
                }

                if (ruleStats[j + 1].searchcycles > 0) {
                    j1eff = (((double) (ruleStats[j + 1].trees))  / ((double) (ruleStats[j + 1].searchcycles))) * 100;
                } else {
                    j1eff = 0;
                }
            }

            if (    (byname && (stringcmp (ruleStats[j].name, ruleStats[j + 1].name) > 0)) 
                ||  (byspace && ((ruleStats[j].trees + ruleStats[j].kids) < (ruleStats[j + 1].trees + ruleStats[j + 1].kids)))
                ||  (bycalls && (ruleStats[j].calls < ruleStats[j + 1].calls)) 
                ||  (parse && (bycycles || bytime) && (ruleStats[j].searchcycles < ruleStats[j + 1].searchcycles)) 
                ||  ((!parse) && (bycycles || bytime)  && ((ruleStats[j].searchcycles + ruleStats[j].matchcycles) 
                        < (ruleStats[j + 1].searchcycles + ruleStats[j + 1].matchcycles))) 
                ||  (parse && byeff && (jeff > j1eff)) 
              ) {
                struct ruleStatsT temp;
                structassign (temp, ruleStats[j]);
                structassign (ruleStats[j], ruleStats[j + 1]);
                structassign (ruleStats[j + 1], temp);
            }
        }
    }

    // Output stats
    int total = 0;

    if (byspace) {
        total = ruleStats[1].trees + ruleStats[1].kids;
    } else if (parse) {
        total = ruleStats[1].searchcycles;
    } else {
        total = ruleStats[1].searchcycles + ruleStats[1].matchcycles;
    }

    if (percall) {
        // Scale time/cycles per call to rule/nonterm
        if (parse) {
            fprintf (stdout, "                                                              KID CELLS            TREE NODES             PARSE CYCLES             BACKTRACK CYCLES   \n");
        } else {
            fprintf (stdout, "                                                              KID CELLS            TREE NODES             SEARCH CYCLES               MATCH CYCLES    \n");
        }

        fprintf (stdout,     "     NAME                      PCT    CALLS    MATCHED     total  per call      total  per call         total     per call         total     per call \n");
        fprintf (stdout,     "     ----                      ---    -----     -----      -----  --------      -----  --------         -----     --------         -----     -------- \n");

        for (int i = 1; i <= nrules; i++) {
            const struct ruleStatsT *r = &(ruleStats[i]);

            if (r->calls != 0) {
                string shortname;
                substring (shortname, r->name, 1, min (30, stringlen (r->name)));
                fprintf (stdout, "%-30s", shortname); 

                if (total != 0) {
                    double percent = 0;

                    if (byspace) {
                        percent = ((((double) ((r->trees) + (r->kids)))  / ((double) total)) * 100);
                    } else if (parse) {
                        percent = ((((double) (r->searchcycles))  / ((double) total)) * 100);
                    } else {
                        percent = ((((double) (r->searchcycles + r->matchcycles))  / ((double) total)) * 100);
                    }
                    int ipercent = percent;
                    fprintf (stdout, "%3d%%", ipercent);
                } else {
                    fprintf (stdout, "    ");
                }

                fprintf (stdout, "%8d",  r->calls);
                fprintf (stdout, "%10d", r->matches);
                fprintf (stdout, "%12d", r->kids);
                fprintf (stdout, "%9d",  r->kids / r->calls);
                fprintf (stdout, "%12d", r->trees);
                fprintf (stdout, "%9d",  r->trees / r->calls);
                fprintf (stdout, "%15d", r->searchcycles);
                fprintf (stdout, "%12d", r->searchcycles / r->calls);
                fprintf (stdout, "%15d", r->matchcycles);
                fprintf (stdout, "%12d", r->matchcycles / r->calls);
                fprintf (stdout, "\n");
            }
        }

    } else {

        if (parse) {
            fprintf (stdout, "     NAME                      PCT    CALLS    MATCHED     CELLS       NODES       CYCLES   BACKTRACKS  EFFICIENCY\n");
            fprintf (stdout, "     ----                      ---    -----     -----      -----       -----      --------  ----------  ----------\n");
        } else {
            fprintf (stdout, "     NAME                      PCT    CALLS    MATCHED     CELLS       NODES      SEARCHES     MATCHES\n");
            fprintf (stdout, "     ----                      ---    -----     -----      -----       -----      --------     -------\n");
        }

        for (int i = 1; i <= nrules; i++) {
            const struct ruleStatsT *r = &(ruleStats[i]);

            if ((r->calls) != 0) {
                string shortname;
                substring (shortname, r->name, 1, min (30, stringlen (r->name)));
                fprintf (stdout, "%-30s", shortname); 

                if (total != 0) {
                    double percent = 0;

                    if (byspace) {
                        percent = ((((double) ((r->trees) + (r->kids)))  / ((double) total)) * 100);
                    } else {
                        if (parse) {
                            percent = ((((double) (r->searchcycles))  / ((double) total)) * 100);
                        } else {
                            percent = ((((double) (r->searchcycles + r->matchcycles))  / ((double) total)) * 100);
                        }
                    }
                    int ipercent = percent;
                    fprintf (stdout, "%3d%%", ipercent);
                } else {
                    fprintf (stdout, "    ");
                }

                fprintf (stdout, "%8d",  r->calls);
                fprintf (stdout, "%10d", r->matches);
                fprintf (stdout, "%12d", r->kids);
                fprintf (stdout, "%12d", r->trees);
                fprintf (stdout, "%12d", r->searchcycles);
                fprintf (stdout, "%12d", r->matchcycles);

                if (parse) {
                    double efficiency = (((double) (r->trees))  / ((double) (r->searchcycles))) * 100;
                    fprintf (stdout, "%12.3f\n", efficiency);
                } else {
                    fprintf (stdout, "\n");
                }
            }
        }
    }
}
